/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2007 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.anttask;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
/**
* Abstract base class for Ant tasks that run programs
* (main() methods) in findbugs.jar or findbugsGUI.jar.
*
* @author David Hovemeyer
*/
public abstract class AbstractFindBugsTask extends Task {
public static final String FINDBUGS_JAR = "findbugs.jar";
public static final long DEFAULT_TIMEOUT = 1200000; // twenty minutes
public static final String RESULT_PROPERTY_SUFFIX = "executeReturnCode";
// A System property to set when FindBugs is run
public static class SystemProperty {
private String name;
private String value;
public SystemProperty() {
}
public void setName(String name) { this.name = name; }
public void setValue(String value) { this.value = value; }
public String getName() { return name; }
public String getValue() { return value; }
}
private String mainClass;
private boolean debug = false;
private File homeDir = null;
private String jvm = "";
private String jvmargs = "";
private long timeout = DEFAULT_TIMEOUT;
private boolean failOnError = false;
private String errorProperty = null;
private List<SystemProperty> systemPropertyList = new ArrayList<SystemProperty>();
private Path classpath = null;
private Path pluginList = null;
private Java findbugsEngine = null;
public String execResultProperty =
"edu.umd.cs.findbugs.anttask.AbstractFindBugsTask" + "." +
RESULT_PROPERTY_SUFFIX;
/**
* Constructor.
*/
protected AbstractFindBugsTask(String mainClass) {
this.mainClass = mainClass;
this.execResultProperty = mainClass +
"." + RESULT_PROPERTY_SUFFIX;
}
/**
* Set the home directory into which findbugs was installed
*/
public void setHome(File homeDir) {
this.homeDir = homeDir;
}
/**
* Set the debug flag
*/
public void setDebug(boolean flag) {
this.debug = flag;
}
/**
* Get the debug flag.
*/
protected boolean getDebug() {
return debug;
}
/**
* Set any specific jvm args
*/
public void setJvmargs(String args) {
this.jvmargs = args;
}
/**
* Set the command used to start the VM
*/
public void setJvm(String jvm) {
this.jvm = jvm;
}
/**
* Set timeout in milliseconds.
* @param timeout the timeout
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Set the failOnError flag
*/
public void setFailOnError(boolean flag) {
this.failOnError = flag;
}
/**
* Tells this task to set the property with the
* given name to "true" when there were errors.
*/
public void setErrorProperty(String name) {
this.errorProperty = name;
}
/**
* Create a SystemProperty (to handle <systemProperty> elements).
*/
public SystemProperty createSystemProperty() {
SystemProperty systemProperty = new SystemProperty();
systemPropertyList.add(systemProperty);
return systemProperty;
}
/**
* Set the classpath to use.
*/
public void setClasspath(Path src) {
if (classpath == null) {
classpath = src;
} else {
classpath.append(src);
}
}
/**
* Path to use for classpath.
*/
public Path createClasspath() {
if (classpath == null) {
classpath = new Path(getProject());
}
return classpath.createPath();
}
/**
* Adds a reference to a classpath defined elsewhere.
*/
public void setClasspathRef(Reference r) {
Path path = createClasspath();
path.setRefid(r);
path.toString(); // Evaluated for its side-effects (throwing a BuildException)
}
/**
* the plugin list to use.
*/
public void setPluginList(Path src) {
if (pluginList == null) {
pluginList = src;
} else {
pluginList.append(src);
}
}
/**
* Path to use for plugin list.
*/
public Path createPluginList() {
if (pluginList == null) {
pluginList = new Path(getProject());
}
return pluginList.createPath();
}
/**
* Adds a reference to a plugin list defined elsewhere.
*/
public void setPluginListRef(Reference r) {
createPluginList().setRefid(r);
}
@Override
public void execute() throws BuildException {
checkParameters();
try {
execFindbugs();
} catch (BuildException e) {
//log("Oops: " + e.getMessage());
if (errorProperty != null) {
getProject().setProperty(errorProperty, "true");
}
if (failOnError) {
throw e;
}
}
}
/**
* Check that all required attributes have been set.
*/
protected void checkParameters() {
if ( homeDir == null && (classpath == null || pluginList == null) ) {
throw new BuildException( "either home attribute or " +
"classpath and pluginList attributes " +
" must be defined for task <"
+ getTaskName() + "/>",
getLocation() );
}
if (pluginList != null) {
// Make sure that all plugins are actually Jar files.
String[] pluginFileList = pluginList.list();
for (String pluginFile : pluginFileList) {
if (!pluginFile.endsWith(".jar")) {
throw new BuildException("plugin file " + pluginFile + " is not a Jar file " +
"in task <" + getTaskName() + "/>",
getLocation());
}
}
}
for (SystemProperty systemProperty : systemPropertyList) {
if (systemProperty.getName() == null || systemProperty.getValue() == null)
throw new BuildException("systemProperty elements must have name and value attributes");
}
}
/**
* Create the FindBugs engine (the Java process that will run
* whatever FindBugs-related program this task is
* going to execute).
*/
protected void createFindbugsEngine() {
findbugsEngine = new Java();
findbugsEngine.setProject( getProject() );
findbugsEngine.setTaskName( getTaskName() );
findbugsEngine.setFork( true );
if (jvm.length()>0)
findbugsEngine.setJvm( jvm );
findbugsEngine.setTimeout( timeout );
if ( debug ) {
jvmargs = jvmargs + " -Dfindbugs.debug=true";
}
findbugsEngine.createJvmarg().setLine( jvmargs );
// Add JVM arguments for system properties
for (SystemProperty systemProperty : systemPropertyList) {
String jvmArg = "-D" + systemProperty.getName() + "=" + systemProperty.getValue();
findbugsEngine.createJvmarg().setValue(jvmArg);
}
if (homeDir != null) {
// Use findbugs.home to locate findbugs.jar and the standard
// plugins. This is the usual means of initialization.
File findbugsLib = new File(homeDir, "lib");
File findbugsLibFindBugs = new File(findbugsLib, "findbugs.jar");
File findBugsFindBugs = new File(homeDir, "findbugs.jar");
//log("executing using home dir [" + homeDir + "]");
if (findbugsLibFindBugs.exists())
findbugsEngine.setClasspath(new Path(getProject(), findbugsLibFindBugs.getPath()));
else if (findBugsFindBugs.exists())
findbugsEngine.setClasspath(new Path(getProject(), findBugsFindBugs.getPath()));
else throw new IllegalArgumentException("Can't find findbugs.jar in " + homeDir);
findbugsEngine.createJvmarg().setValue("-Dfindbugs.home=" + homeDir.getPath());
} else {
// Use an explicitly specified classpath and list of plugin Jars
// to initialize. This is useful for other tools which may have
// FindBugs installed using a non-standard directory layout.
findbugsEngine.setClasspath(classpath);
addArg("-pluginList");
addArg(pluginList.toString());
}
// Set the main class to be whatever the subclass's constructor
// specified.
findbugsEngine.setClassname(mainClass);
}
/**
* Get the Findbugs engine.
*/
protected Java getFindbugsEngine() {
return findbugsEngine;
}
/**
* Add an argument to the JVM used to execute FindBugs.
* @param arg the argument
*/
protected void addArg(String arg) {
findbugsEngine.createArg().setValue(arg.trim());
}
/**
* Sets the given string to be piped to standard input of the FindBugs
* JVM upon launching.
*/
protected void setInputString(String input) {
findbugsEngine.setInputString(input);
}
/**
* Create a new JVM to do the work.
*
* @since Ant 1.5
*/
private void execFindbugs() throws BuildException {
System.out.println("Executing findbugs from ant task");
createFindbugsEngine();
configureFindbugsEngine();
beforeExecuteJavaProcess();
if (getDebug()) {
log(getFindbugsEngine().getCommandLine().describeCommand());
}
/*
* set property containing return code of child process using
* a task identifier and a UUID to ensure exit code corresponds
* to this execution (the base Ant Task won't overwrite return code
* once it's been set, so unique identifiers must be used for
* each execution if we want to get the exit code)
*/
String execReturnCodeIdentifier = execResultProperty + "." +
UUID.randomUUID().toString();
getFindbugsEngine().setResultProperty(execReturnCodeIdentifier);
/*
* if the execution fails, we'll report it ourself -- prevent the
* underlying Ant Java object from throwing an exception
*/
getFindbugsEngine().setFailonerror(false);
try {
getFindbugsEngine().execute();
} catch(BuildException be) {
// setFailonerror(false) should ensure that this doesn't happen, but...
log(be.toString());
}
String returnProperty =
getFindbugsEngine().getProject().getProperty(execReturnCodeIdentifier);
int rc = returnProperty == null ? 0 : Integer.valueOf(returnProperty).intValue();
afterExecuteJavaProcess(rc);
}
protected abstract void configureFindbugsEngine();
protected abstract void beforeExecuteJavaProcess();
protected abstract void afterExecuteJavaProcess(int rc);
}